/*	Interactive Gravitational Simulator
	Project: IGS Visualization
	File: FontEngine.cpp
	Author: Mike Bantegui <mbante2@gmail.com>, Hofstra University 
	Copyright (C) 2012 - 2013 Mike Bantegui

	This file is part of the IGS software package for simulating
	the N-Body problem in real-time. This was developed as part
	of a departmental honors thesis project at Hofstra University
	in the Spring 2012 semester.

	IGS is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	IGS is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

/* 
	Note: The following font engine implementation has been inspired
	by the font rendering engine within Gravit written by Gerald
	Kaszuba et al. The Gravit implementation may be found at either
	the github repository https://github.com/gak/gravit or on the
	gravit homepage http://gravit.slowchop.com/
*/

#include "FontEngine.h"
#include <cassert>
#include <iostream>
#include <SDL_ttf.h>
#include <SDL_opengl.h>

namespace IGS
{

// Get the next power of two
// See: http://stackoverflow.com/questions/364985/
// Asked by Boyan http://stackoverflow.com/users/38106/
// Solution due to Larry Gritz http://stackoverflow.com/users/3832/
int NextPowerOfTwo(int x)
{
	if (x < 0)
		return 0;

	x--;
	x |= x >> 1;
	x |= x >> 2;
	x |= x >> 4;
	x |= x >> 8;
	x |= x >> 16;
	return x + 1;
}

// Constructs a letter in an unused state
FontEngine::Letter::Letter() : id(0)
{
}

// Constructs a letter in a state ready for texture binding
FontEngine::Letter::Letter(int width, int height) : id(0)
{
	Width = width;
	Height = height;
	Size = std::max(NextPowerOfTwo(width),
					NextPowerOfTwo(height));
}

// Binds the data from the SDL_Surface to a texture
void FontEngine::Letter::BindTexture(SDL_Surface *surface)
{
	glGenTextures(1, &id);
	glBindTexture(GL_TEXTURE_2D, id);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexImage2D(GL_TEXTURE_2D, 0, 3, surface->w, surface->h,
		0, GL_RGBA, GL_UNSIGNED_BYTE, surface->pixels);
	
	glBindTexture(GL_TEXTURE_2D, 0);
}

// Frees the texture data associated with the Letter
void FontEngine::Letter::FreeTexture()
{
	glDeleteTextures(1, &id);
	id = 0;
}

// Render the letter at the coordinates (x, y) on the screen
int FontEngine::Letter::Render(double x, double y)
{
	glBindTexture(GL_TEXTURE_2D, id);

	glPushMatrix();
	glTranslated(x, y, 0);
	glBegin(GL_QUADS);
	glTexCoord2f(0, 0); glVertex2i(0, 0);
	glTexCoord2f(0, 1); glVertex2i(0, Size);
	glTexCoord2f(1, 1); glVertex2i(Size, Size);
	glTexCoord2f(1, 0); glVertex2i(Size, 0);
	glEnd();
	glPopMatrix();

	return Width;
}

// Blit the rendered character to a surface and bind the texture
// data to enable rendering of the Letter. Returns whether this operation
// was successful.
bool FontEngine::Letter::PreRender(SDL_Surface *rendered)
{
	SDL_Surface *surface = SDL_CreateRGBSurface(SDL_SWSURFACE, Size,
		Size, 32, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000);

	if (surface == NULL)
	{
		std::cerr << "Failed to create RGB surface in font load" << std::endl;
		return false;
	}

	if (SDL_BlitSurface(rendered, NULL, surface, NULL))
	{
		SDL_FreeSurface(surface);
		std::cerr << "Failed to blit surface in font load" << std::endl;
		return false;
	}

	BindTexture(surface);
	SDL_FreeSurface(surface);
	return true;
}

// Construct the font engine in an unintialized state
FontEngine::FontEngine() : IsValid(false)
{
}

// Renders the specified letter to a SDL_Surface
SDL_Surface *RenderLetter(TTF_Font *font, char letter)
{
	char data[2] = { letter, '\0' };
	SDL_Color color = { 255, 255, 255, 255 };
	return TTF_RenderText_Solid(font, data, color);
}

// Loads the font from a file and renders it at a given font size.
// Returns whether the font load was successful.
bool FontEngine::LoadFont(const char *filename, int size)
{
	if (TTF_Init() != 0)
	{
		std::cerr << "SDL_TTF initialization failed" << std::endl;
		std::cerr << SDL_GetError() << std::endl;
		return false;
	}

	TTF_Font *font = TTF_OpenFont(filename, size);
	if (font == NULL)
	{
		std::cerr << "Could not open " << filename << std::endl;
		return false;
	}

	Letter letters[256];
	int height = 0;

	// Pre-render process:
	// 1) Render the letter using the specified font
	// 2) Blit the rendered font to an RGB surface
	// 3) Bind the blitted letter to a texture

	for (int i = 1; i < 256; i++)
	{
		SDL_Surface *rendered = RenderLetter(font, (char) i);

		if (rendered == NULL)
		{
			TTF_CloseFont(font);
			std::cerr << "Failed to render letter " << i << std::endl;
			std::cerr << SDL_GetError() << std::endl;
			return false;
		}

		letters[i] = Letter(rendered->w, rendered->h);
		height = std::max(height, letters[i].Height);

		if (!letters[i].PreRender(rendered))
		{
			SDL_FreeSurface(rendered);
			TTF_CloseFont(font);
			std::cerr << SDL_GetError() << std::endl;
			return false;
		}

		SDL_FreeSurface(rendered);
	}

	// Copy over all the data only after we have succesfully
	// pre-rendered all the letters. We do this so that even
	// if we fail to load a font, we still have the old font
	// available for usage.

	_FontHeight = height;
	IsValid = true;

	for (int i = 0; i < 256; i++)
	{
		Letters[i].FreeTexture();
		Letters[i] = letters[i];
	}

	TTF_CloseFont(font);
	TTF_Quit();

	return true;
}

// Renders left-aligned text at the specified screen coordinates
void FontEngine::Render(double x, double y, std::string word)
{
	Render(x, y, word, AlignLeft);
}

// Renders left or right aligned text at the specified screen coordinates
void FontEngine::Render(double x, double y, std::string word, TextAlignment align)
{
	assert(IsValid);

	if (align == AlignLeft)
	{
		for (size_t i = 0; i < word.size(); i++)
			x += RenderInner(x, y, word[i]);
	}

	else // if(align == AlignRight)
	{
		for (size_t i = word.size() - 1; i > 0; i--)
		{
			RenderInner(x, y, word[i]);
			x -= Letters[(unsigned char) word[i-1]].Width;
		}
		RenderInner(x, y, word[0]);
	}
	
	glBindTexture(GL_TEXTURE_2D, 0);
}

// Get the width of the specified word
int FontEngine::WordWidth(std::string word)
{
	assert(IsValid);

	int width = 0;
	for (size_t i = 0; i < word.size(); i++)
		width += Letters[(unsigned char)word[i]].Width;
	return width;
}

// Get the width of the first n characters of the specified word
int FontEngine::WordWidth(std::string word, int n)
{
	return WordWidth(word.substr(0, n));
}

// Get the height of the currently loaded font
int FontEngine::FontHeight() const
{
	assert(IsValid);
	return _FontHeight;
}

// Renders the specified character on the provided onscreen coordinates
int FontEngine::RenderInner(double x, double y, char letter)
{
	return Letters[(unsigned char)letter].Render(x, y);
}

}